package app.security;

import app.Const;
import app.models.system.Resource;
import app.models.system.Role;
import app.models.system.RoleResource;
import app.models.system.User;
import com.ifonly.activerecord.ActiveRecordFactory;
import com.ifonly.activerecord.DbUtils;
import com.ifonly.activerecord.sql.config.Sql;
import com.ifonly.security.AbstractAppShiroRealm;
import com.ifonly.security.SecurityEntry;
import com.ifonly.security.SecurityInfo;
import com.ifonly.utils.CryptoUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author <a href="mailto:ifonlymaster@163.com">ifonly</a>
 * @version 1.0 2015-12-29 20:12
 * @since JDK 1.6
 */
public class DefaultAppShiroRealm extends AbstractAppShiroRealm {

    @Override
    public String getName() {
        return "DefaultAppShiroRealm";
    }

    @SuppressWarnings("unchecked")
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SecurityEntry<Long, SecurityInfo<User, Role, List<Resource>>> securityEntry =
                (SecurityEntry<Long, SecurityInfo<User, Role, List<Resource>>>) principalCollection.getPrimaryPrincipal();
        SecurityInfo<User, Role, List<Resource>> securityInfo = securityEntry.getInfo();

        Role role = securityInfo.getR();
        String roleName = role.getString("name");
        Set<String> roleNameSet = new HashSet<String>();
        roleNameSet.add(roleName);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roleNameSet);

        List<Resource> resources = securityInfo.getP();
        Set<String> resourceNameSet = new HashSet<String>();
        for (Resource resource: resources) {
            resourceNameSet.add(resource.getString("name"));
        }
        authorizationInfo.setStringPermissions(resourceNameSet);

        return authorizationInfo;
    }

    /**
     * 身份认证
     *
     *  AuthenticationException或其子类
     *
     *   DisabledAccountException       - 禁用的帐号
     *   LockedAccountException         - 锁定的帐号
     *   UnknownAccountException        - 错误的帐号
     *   ExcessiveAttemptsException     - 登录失败次数过多
     *   IncorrectCredentialsException  - 错误的凭证
     *   ExpiredCredentialsException    - 过期的凭证
     *
     *
     * @param token 身份认证 token 信息
     * @return 身份认证结果
     * @throws AuthenticationException 身份验证失败请捕获AuthenticationException或其子类
     *
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        Object principal = token.getPrincipal();

        User user = DbUtils.query(User.class, Sql.get("user.findByUsername"), principal);

        if (user == null) {
            throw new UnknownAccountException("账号密码错误");
        }

        if (user.getInt("status") != Const.Status.ENABLED) {
            throw new LockedAccountException("帐号禁用");
        }

        Role role = DbUtils.query(Role.class, Sql.get("userRole.findRolesByUserId"), user.getLong("id"));
        if (role == null) {
            throw new DisabledAccountException("没有查找到对应的角色, 账号禁用");
        }

        RoleResource roleResource = ActiveRecordFactory.getActiveRecord(RoleResource.class);

        List<Resource> resources = roleResource.findResourcesByRoleId(role.getInteger("id"));

        SecurityEntry<Long, SecurityInfo<User, Role, List<Resource>>> entry = new SecurityEntry<Long, SecurityInfo<User, Role, List<Resource>>>(
                user.getLong("id"),
                user.getString("real_name"),
                user.getString("username")
        );

        SecurityInfo<User, Role, List<Resource>> securityInfo
                = new SecurityInfo<User, Role, List<Resource>>(user, role, resources);

        entry.setInfo(securityInfo);

        byte[] salt = CryptoUtils.decodeHex(user.getString("salt"));
        final ByteSource salt_bs = ByteSource.Util.bytes(salt);

        return new SimpleAuthenticationInfo(
                entry,
                user.getString("password"),
                salt_bs,
                getName()
        );
    }


    /**
     * 设置用户的密码，盐值信息
     *
     * @param user 用户
     */
    public static void setPasswordAndSalt(User user) {
        byte[] saltBytes = CryptoUtils.generateSalt(CryptoUtils.SALT_SIZE);
        String salt = CryptoUtils.encodeHex(saltBytes);

        byte[] hashPassword = CryptoUtils.sha1(Const.DEFAULT_PASSWORD.getBytes(), saltBytes, CryptoUtils.HASH_INTERATIONS);
        String password = CryptoUtils.encodeHex(hashPassword);

        user.set("password", password);
        user.set("salt", salt);
    }

}
